package com.stone.framework.task.service.impl;

import java.util.List;
import java.util.Objects;

import javax.annotation.PostConstruct;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.stone.framework.core.exception.BusinessException;
import com.stone.framework.core.util.Beans;
import com.stone.framework.core.util.Strings;
import com.stone.framework.message.task.ScheduleConfigPageReqBody;
import com.stone.framework.message.task.ScheduleConfigPageResBody;
import com.stone.framework.message.task.ScheduleConfigReqBody;
import com.stone.framework.task.entity.ScheduleConfig;
import com.stone.framework.task.mapper.ScheduleConfigMapper;
import com.stone.framework.task.schedule.ScheduleJob;
import com.stone.framework.task.service.ScheduleService;

@Service
@Transactional(readOnly = true)
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private ScheduleConfigMapper scheduleConfigMapper;

    @PostConstruct
    public void init() {
        List<ScheduleConfig> list = scheduleConfigMapper.selectList(null);
        for (ScheduleConfig entity : list) {
            CronTrigger trigger = this.getCronTrigger(entity);
            if (trigger == null) {
                this.createScheduleJob(entity);
            } else {
                this.updateScheduleJob(entity);
            }
        }
    }

    @Override
    public IPage<ScheduleConfigPageResBody> queryPage(ScheduleConfigPageReqBody reqBody, Page<ScheduleConfig> page) {
        ScheduleConfig entity = new ScheduleConfig();
        Beans.copyProperties(reqBody, entity);
        return scheduleConfigMapper.selectPage(page, this.condition(entity)).convert(e -> {
            ScheduleConfigPageResBody resBody = new ScheduleConfigPageResBody();
            Beans.copyProperties(e, resBody);
            return resBody;
        });
    }

    @Override
    @Transactional(readOnly = false)
    public void save(ScheduleConfigReqBody reqBody) {
        ScheduleConfig entity = new ScheduleConfig();
        Beans.copyProperties(reqBody, entity);
        scheduleConfigMapper.insert(entity);

        this.createScheduleJob(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void modify(ScheduleConfigReqBody reqBody) {
        ScheduleConfig entity = scheduleConfigMapper.selectById(reqBody.getId());
        if (entity == null) {
            throw new BusinessException("配置不存在");
        }
        Beans.copyProperties(reqBody, entity);
        scheduleConfigMapper.updateById(entity);

        this.updateScheduleJob(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void run(Long id) {
        ScheduleConfig entity = scheduleConfigMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("配置不存在");
        }

        this.runScheduleJob(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void pause(Long id) {
        ScheduleConfig entity = scheduleConfigMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("配置不存在");
        }
        entity.setState(ScheduleConfig.State.DISABLED.name());
        scheduleConfigMapper.updateById(entity);

        this.pauseScheduleJob(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void resume(Long id) {
        ScheduleConfig entity = scheduleConfigMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("配置不存在");
        }
        entity.setState(ScheduleConfig.State.ENABLED.name());
        scheduleConfigMapper.updateById(entity);

        this.resumeScheduleJob(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void delete(Long id) {
        ScheduleConfig entity = scheduleConfigMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("配置不存在");
        }
        scheduleConfigMapper.deleteById(entity.getId());

        this.deleteScheduleJob(entity);
    }

    private QueryWrapper<ScheduleConfig> condition(ScheduleConfig condition) {
        QueryWrapper<ScheduleConfig> wrapper = new QueryWrapper<>();
        wrapper.like(Strings.notEmpty(condition.getJobName()), "job_name", condition.getJobName());
        wrapper.like(Strings.notEmpty(condition.getRemark()), "remark", condition.getRemark());
        wrapper.eq(Strings.notEmpty(condition.getServiceName()), "service_name", condition.getServiceName());
        wrapper.eq(Objects.nonNull(condition.getState()), "state", condition.getState());
        return wrapper;
    }

    private void createScheduleJob(ScheduleConfig entity) {
        JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(this.getJobKey(entity)).build();
        CronScheduleBuilder scheduleBuilder =
            CronScheduleBuilder.cronSchedule(entity.getCron()).withMisfireHandlingInstructionDoNothing();
        CronTrigger trigger =
            TriggerBuilder.newTrigger().withIdentity(this.getTriggerKey(entity)).withSchedule(scheduleBuilder).build();
        jobDetail.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY, entity);

        try {
            this.scheduler.scheduleJob(jobDetail, trigger);

            if (ScheduleConfig.State.DISABLED.name().equals(entity.getState())) {
                this.scheduler.pauseJob(this.getJobKey(entity));
            }
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务生成异常", e);
        }

    }

    private void updateScheduleJob(ScheduleConfig entity) {
        CronScheduleBuilder scheduleBuilder =
            CronScheduleBuilder.cronSchedule(entity.getCron()).withMisfireHandlingInstructionDoNothing();

        CronTrigger trigger = this.getCronTrigger(entity);
        trigger.getTriggerBuilder().withIdentity(this.getTriggerKey(entity)).withSchedule(scheduleBuilder).build();
        trigger.getJobDataMap().put(ScheduleJob.JOB_PARAM_KEY, entity);
        try {
            this.scheduler.rescheduleJob(this.getTriggerKey(entity), trigger);

            if (ScheduleConfig.State.DISABLED.name().equals(entity.getState())) {
                this.scheduler.pauseJob(this.getJobKey(entity));
            }
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务更新异常", e);
        }

    }

    private void deleteScheduleJob(ScheduleConfig entity) {
        try {
            this.scheduler.deleteJob(this.getJobKey(entity));
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务删除异常", e);
        }
    }

    private void runScheduleJob(ScheduleConfig entity) {
        try {
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ScheduleJob.JOB_PARAM_KEY, entity);
            scheduler.triggerJob(getJobKey(entity), dataMap);
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务执行异常", e);
        }

    }

    private void pauseScheduleJob(ScheduleConfig entity) {
        try {
            this.scheduler.pauseJob(this.getJobKey(entity));
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务暂停异常", e);
        }

    }

    private void resumeScheduleJob(ScheduleConfig entity) {
        try {
            this.scheduler.resumeJob(this.getJobKey(entity));
        } catch (SchedulerException e) {
            throw new BusinessException("定时任务恢复异常", e);
        }

    }

    private JobKey getJobKey(ScheduleConfig entity) {
        return JobKey.jobKey(JOB_KEY + entity.getId());
    }

    public TriggerKey getTriggerKey(ScheduleConfig entity) {
        return TriggerKey.triggerKey(JOB_KEY + entity.getId());
    }

    public CronTrigger getCronTrigger(ScheduleConfig entity) {
        try {
            return (CronTrigger)this.scheduler.getTrigger(this.getTriggerKey(entity));
        } catch (SchedulerException e) {
            throw new BusinessException("获取CronTrigger异常", e);
        }
    }

}
